package logger

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"io"
	"os"
	"path"
	"sync"
	"time"
)

type DateRotateConfig struct {
	Config
	// 存储文件格式。如果为空，则使用 defaultDateRotateLogName
	// Y% 年，如 2021
	// m% 月，如 9
	// d% 日，如 1
	// H% 小时，如 10
	// i% 分，如 2
	// s% 秒，如 55
	Format string `yaml:"format"`
	// 文件最多多少M
	MaxSize int `yaml:"max_size"`
	// 文件最多保留多长时间
	MaxAge int `yaml:"max_age"`
	// 文件最多保留多少个文件
	MaxBackups int `yaml:"max_backups"`
	// 是否启用压缩
	Compress bool `yaml:"compress"`
	// 与 level 参数一样，用于限制可保存的日志级别
	// 优先级比 level 高
	LevelEnableFunc zap.LevelEnablerFunc
	// 重命名文件周期
	ChangeNameInterval time.Duration
}

var defaultDateRotateLogName = "app.log"

type DateRotateLogger struct {
	l              *lumberjack.Logger
	mu             sync.Mutex
	config         *DateRotateConfig
	defaultLogName string
	filename       string
}

func (d *DateRotateLogger) Write(p []byte) (int, error) {
	d.mu.Lock()
	var err error
	if d.filename == "" {
		if d.filename, err = d.currentFilename(); err != nil {
			d.mu.Unlock()
			return 0, err
		}
		d.l.Filename = d.filename
		d.l.Close()
		d.changeName()
	}
	d.mu.Unlock()
	return d.l.Write(p)
}

func (d *DateRotateLogger) currentFilename() (string, error) {
	if d.config.Format == "" {
		return path.Join(d.config.Path, defaultDateRotateLogName), nil
	}
	logPath := d.config.Path
	info, err := os.Stat(logPath)
	if err != nil {
		return "", err
	}
	if !info.IsDir() {
		logPath = path.Dir(logPath)
	}
	newLogPath := path.Join(d.config.Path, FormatFile(d.config.Format))
	return newLogPath, nil
}

func (d *DateRotateLogger) changeName() {
	interval := d.config.ChangeNameInterval
	if interval <= 0 {
		interval = d.defaultInterval()
	}
	time.AfterFunc(interval, func() {
		d.resetFilename()
	})
}

// 默认重命名周期：1天
func (d *DateRotateLogger) defaultInterval() time.Duration {
	now := time.Now()
	y, m, day := now.Date()
	tomorrow := time.Date(y, m, day+1, 0, 0, 0, 0, now.Location())
	return tomorrow.Sub(time.Now())
}

func (d *DateRotateLogger) resetFilename() {
	d.mu.Lock()
	d.filename = ""
	d.mu.Unlock()
}

func NewDateRotate(config *DateRotateConfig) (*zap.SugaredLogger, error) {
	var opts []zap.Option
	if config.Level == zap.DebugLevel {
		opts = append(opts, zap.AddCaller(), zap.Development(), zap.AddStacktrace(zap.DebugLevel))
	} else {
		opts = append(opts, zap.AddStacktrace(zap.ErrorLevel))
	}
	return zap.New(generateCore(config), opts...).Sugar(), nil
}

func generateCore(config *DateRotateConfig) zapcore.Core {
	cfg := zap.NewProductionConfig()
	cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	cfg.EncoderConfig.TimeKey = "time"

	lv := config.LevelEnableFunc
	if lv == nil {
		lv = func(lvl zapcore.Level) bool {
			return lvl >= config.Level
		}
	}

	var writer io.Writer
	if config.Format != "" {
		writer = &DateRotateLogger{l: &lumberjack.Logger{
			Filename:   config.Path,
			MaxSize:    config.MaxSize,
			MaxBackups: config.MaxBackups,
			MaxAge:     config.MaxAge,
			Compress:   config.Compress,
			LocalTime:  true,
		},
			config: config,
		}
	} else {
		writer = &lumberjack.Logger{
			Filename:   path.Join(config.Path, defaultDateRotateLogName),
			MaxSize:    config.MaxSize,
			MaxBackups: config.MaxBackups,
			MaxAge:     config.MaxAge,
			Compress:   config.Compress,
			LocalTime:  true,
		}
	}

	w := zapcore.AddSync(writer)

	return zapcore.NewCore(
		zapcore.NewJSONEncoder(cfg.EncoderConfig),
		zapcore.AddSync(w),
		lv,
	)
}

// NewDateRotateTee Level 以第 0 个 config 为准
func NewDateRotateTee(config []*DateRotateConfig) (*zap.SugaredLogger, error) {
	var cores []zapcore.Core

	var opts []zap.Option
	if config[0].Level == zap.DebugLevel {
		opts = append(opts, zap.AddCaller(), zap.Development(), zap.AddStacktrace(zap.DebugLevel))
	} else {
		opts = append(opts, zap.AddStacktrace(zap.ErrorLevel))
	}
	for _, v := range config {
		cores = append(cores, generateCore(v))
	}
	return zap.New(zapcore.NewTee(cores...), opts...).Sugar(), nil
}
